﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace Djs.Common.Components
{
    /// <summary>
    /// Time axis calculations, convertor to visual representation and direct visualisation to graphic layer
    /// </summary>
    public class GTimeAxis : InteractiveItem, IInteractiveItem
    {
        #region Constructor, public properties and events
        public GTimeAxis()
        {
            this._Arrangement = new Arrangement(this);
            this.InitProperties();
        }
        /// <summary>
        /// Visible time range of time axis.
        /// Setting invoke events TimeRangeChange and DrawRequest.
        /// </summary>
        public TimeRange TimeRange
        {
            get { return this._TimeRange; }
            set
            {
                TimeRange clipValue = this._AlignTimeRange(value);
                if (clipValue != null && clipValue != this._TimeRange)
                {
                    this.TimeRangeInternal = clipValue;
                    this.OnTimeRangeChange();
                    this.OnDrawRequest();
                }
            }
        }
        /// <summary>
        /// Visible time range of time axis.
        /// Setting invoke no events, only internal recalculate visual data.
        /// </summary>
        public TimeRange TimeRangeInternal
        {
            get { return this._TimeRange; }
            set
            {
                TimeRange clipValue = this._AlignTimeRange(value);
                if (clipValue != null && clipValue != this._TimeRange)
                {
                    this._TimeRange = clipValue;
                    this._CalculateScale();
                }
            }
        }
        /// <summary>
        /// Create and align new TimeRange for current TimeAxis.
        /// New value can be "one-side" (HasOnlyBegin or HasOnlyEnd).
        /// New range is aligned to valid TimeScale for current TimeAxis.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private TimeRange _AlignTimeRange(TimeRange value)
        {
            TimeRange timeRange = null;
            if (value.IsFilled)
                timeRange = value;
            else if (value.HasOnlyBegin)
                timeRange = TimeRange.CreateFromBeginSize(value.Begin.Value, this._TimeRange.Time.Value);
            else if (value.HasOnlyEnd)
                timeRange = TimeRange.CreateFromSizeEnd(this._TimeRange.Time.Value, value.End.Value);
            else
                return null;

            // TimeScale without aligning (to MIN-MAX range):
            if (this.PixelSize <= 0m) return timeRange;
            decimal? timeScale = _GetTimeScale(timeRange, false);
            if (!timeScale.HasValue) return null;
            if (_IsTimeScaleInRange(timeScale.Value)) return timeRange;

            // Zoom TimeRange to valid TimeScale, with center on center of TimeRange:
            decimal validTimeScale = _AlignTimeScale(timeScale.Value);
            double secondsInScale = (double)(validTimeScale * this.PixelSize); // timescale = seconds / 1px; secondsInScale = seconds on current pixel size
            return timeRange.ZoomToSize(0.5d, TimeSpan.FromSeconds(secondsInScale)); // new TimeRange with specified Time, with center on center original TimeRange
        }
        /// <summary>
        /// Number of seconds to one pixel (in range 1 milisec to 1 year to 20 pixels)
        /// </summary>
        public decimal TimeScale
        {
            get { return this._TimeScale; }
            protected set
            { this._TimeScale = _AlignTimeScale(value); }
        }
        /// <summary>
        /// Minimal scale = smallest TimeRange : 1 milisecond to 20 pixel.
        /// Scale = seconds / pixel, thus 1ms/20px = 0.001 / 20 = 0.00005
        /// </summary>
        protected const decimal TIME_SCALE_MIN = 0.00005m;
        /// <summary>
        /// Maximal scale = greatest TimeRange : 1 year to 20 pixel
        /// Scale = seconds / pixel, thus 1yr/20px = 31556926,08 / 20 = 1577846.304
        /// </summary>
        protected const decimal TIME_SCALE_MAX = 1577846.304m;
        /// <summary>
        /// Current size in pixel (as decimal)
        /// </summary>
        protected decimal PixelSize { get { return (decimal)this._Bounds.Width; } }
        /// <summary>
        /// Coordinates on a control (typically GInteractiveControl), where TimeAxis are drawed and are active.
        /// Setting invoke events AreaChange and DrawRequest.
        /// </summary>
        public Rectangle Bounds
        {
            get { return this._Bounds; }
            set
            {
                if (value != this._Bounds)
                {
                    this._Bounds = value;
                    this._CalculateEnd();
                    this.OnAreaChange();
                    this.OnDrawRequest();
                }
            }
        }
        /// <summary>
        /// Coordinates on a control (typically GInteractiveControl), where TimeAxis are drawed and are active.
        /// Setting invoke no events, only internal recalculate visual data.
        /// </summary>
        public Rectangle BoundsInternal
        {
            get { return this._Bounds; }
            set
            {
                if (value != this._Bounds)
                {
                    this._Bounds = value;
                    this._CalculateTicks();
                }
            }
        }
        /// <summary>
        /// Color of background of axis
        /// </summary>
        public Color BackColor { get; set; }
        public float BackColor3DEffect { get; set; }
        private void InitProperties()
        {
            this.Enabled = true;
            this.BackColor = Color.DodgerBlue;
            this.BackColor3DEffect = 0.25f;
        }
        /// <summary>
        /// Current list of all ticks
        /// </summary>
        public IEnumerable<TimeAxisTick> TickList { get { return this._TickList; } }
        protected override GInteractiveStyles Style { get { return GInteractiveStyles.AllInteractivity; } }
        protected virtual void OnTimeRangeChange()
        {

        }
        protected virtual void OnAreaChange()
        {
            
        }
        protected virtual void OnDrawRequest()
        {
            
        }
        private Rectangle _Bounds
        {
            get { return this.__Bounds; }
            set
            {
                this.__Bounds = value;
                this.ActiveBounds = value;
                this.RepaintAllItems = true;
            }
        }
        private Rectangle __Bounds;
        private TimeRange _TimeRange;
        #endregion
        #region Interactive property and methods
        /// <summary>
        /// true when this control is interactive (current SizeRange can be moved / zoom / shift).
        /// true is default.
        /// </summary>
        public bool Enabled { get { return this.IsEnabled; } set { this.IsEnabled = value; } }
        protected override void StateChanged(GInteractiveChangeStateArgs e)
        {
            e.ToolTip = "";
            switch (e.ChangeState)
            {
                case GInteractiveChangeState.MouseOver:
                    this._MouseOverPoint = e.MouseRelativePoint;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;
                case GInteractiveChangeState.MouseOverDisabled:
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;
                case GInteractiveChangeState.LeftDown:
                    this._MouseOverPoint = null;
                    this._MouseDownPoint = e.MouseRelativePoint;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;
                case GInteractiveChangeState.LeftDragBegin:
                    if ((Control.ModifierKeys & Keys.Control) == 0)
                    {   // Shift:
                        this._InteractiveShiftOrigin = this.TimeRange.Begin;
                        e.UserDragPoint = this.Bounds.Location;
                        e.RequiredCursorType = SysCursorType.Hand;
                    }
                    else
                    {   // Zoom:
                        this._InteractiveZoomOrigin = this.TimeRange.Clone;
                        this._InteractiveZoomCenter = this.GetValueOnPixelOnAxis(e.MouseRelativePoint.Value.X);
                        e.UserDragPoint = this.Bounds.Location;
                        e.RequiredCursorType = SysCursorType.SizeWE;
                    }
                    break;
                case GInteractiveChangeState.LeftDragMove:
                    if (e.UserDragPoint.HasValue && this._InteractiveShiftOrigin.HasValue)
                    {   // Shift:
                        int shiftX = this.Bounds.X - e.UserDragPoint.Value.X;
                        DateTime begin = this._InteractiveShiftOrigin.Value.Add(this.GetTimeSpanForSize(shiftX).Value);
                        begin = this._CurrentSet.RoundDateToTick(begin, AxisTickType.PixelTick).Value;
                        this.TimeRangeInternal = new TimeRange(begin, null);

                        if (this._MouseDownPoint.HasValue)
                            this._MouseDownPoint = new Point(e.MouseRelativePoint.Value.X, this._MouseDownPoint.Value.Y);
                        this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                        e.ToolTip = this._CreateToolTip(this.TimeRangeInternal);
                    }
                    else if (e.UserDragPoint.HasValue && this._InteractiveZoomCenter.HasValue)
                    {   // Zoom:
                        int shiftX = this.Bounds.X - e.UserDragPoint.Value.X;  // Shift on X axis:  negative number = to left: more seconds visible, but less details;   positive number = to right: less seconds visible, but more details
                        int silent = 5;                                        // Silent zone = 5 pixel with ratio = 1
                        shiftX = (shiftX >= silent ? shiftX - silent : (shiftX < -silent ? shiftX + silent : 0));
                        TimeRange zoomTime = null;
                        if (shiftX == 0)
                        {
                            zoomTime = this._InteractiveZoomOrigin.Clone;
                        }
                        else
                        {
                            double expon = (double)shiftX / 40d;                   // Each 40 pixels double / half scale
                            double ratio = Math.Pow(2D, expon);                    // Coefficient to zoom of original TimeRange
                            zoomTime = this._InteractiveZoomOrigin.ZoomToRatio(this._InteractiveZoomCenter.Value, ratio);
                            decimal? timeScale = this._GetTimeScale(zoomTime, false);
                            if (!_IsTimeScaleInRange(timeScale))
                            {   // Out of scale - calculate total visible seconds (from aligned scale * pixels) and create TimeRange for this seconds and center:
                                decimal seconds = _AlignTimeScale(timeScale.Value) * this.PixelSize;
                                zoomTime = this._InteractiveZoomOrigin.ZoomToSize(this._InteractiveZoomCenter.Value, TimeSpan.FromSeconds((double)seconds));
                            }
                        }
                        if (zoomTime != this._TimeRange)
                        {
                            this.TimeRangeInternal = zoomTime;
                            this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                        }
                        e.ToolTip = this._CreateToolTip(this.TimeRangeInternal);
                    }
                    break;
                case GInteractiveChangeState.LeftDragEnd:
                    this._InteractiveShiftOrigin = null;
                    this._InteractiveZoomCenter = null;
                    this._InteractiveZoomOrigin = null;
                    e.RequiredCursorType = SysCursorType.Default;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;

                case GInteractiveChangeState.LeftUp:
                    this._MouseDownPoint = null;
                    this._MouseOverPoint = e.MouseRelativePoint;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;

                case GInteractiveChangeState.WheelUp:
                case GInteractiveChangeState.WheelDown:
                    TimeRange currentTime = this.TimeRange;
                    if (Control.ModifierKeys == Keys.Control)
                    {   // Ctrl + Wheel = Zoom under mouse:
                        DateTime? center = this.GetValueOnPixelOnAxis(e.MouseRelativePoint.Value.X);
                        if (center.HasValue)
                        {
                            double ratio = 1.15d;
                            if (e.ChangeState == GInteractiveChangeState.WheelUp) ratio = 1d / ratio;
                            this.TimeRangeInternal = currentTime.ZoomToRatio(center.Value, ratio);
                        }
                    }
                    else
                    {   // Wheel alone = Shift:
                        double coefficient = (Control.ModifierKeys == Keys.Shift ? 0.333d : 0.07d) * (e.ChangeState == GInteractiveChangeState.WheelUp ? 1d : -1d);
                        TimeSpan offset = TimeSpan.FromSeconds(coefficient * currentTime.Time.Value.TotalSeconds);
                        this.TimeRangeInternal = new TimeRange(this.TimeRangeInternal.Begin.Value.Add(offset), null);
                    }
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    e.ToolTip = this._CreateToolTip(e.MouseRelativePoint);
                    break;

                case GInteractiveChangeState.MouseLeave:
                    this._MouseOverPoint = null;
                    this.RepaintToLayers = GInteractiveDrawLayer.Standard;
                    break;
            }
        }
        /// <summary>
        /// Create and return text for tooltip for specified relative point
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <returns></returns>
        private string _CreateToolTip(Point? relativePoint)
        {
            if (!relativePoint.HasValue) return null;
            DateTime? value = this.GetValueOnPixelOnAxis(relativePoint.Value.X, AxisTickType.PixelTick);
            if (!value.HasValue) return null;
            if (this._CurrentSet.PixelTickItem.Interval.Days > 0)
            // if (time.Value.TimeOfDay.Ticks == 0)
                return TimeRange.ToTextDow(value.Value, false) + ", " + TimeRange.ToTextYD(value.Value);
            return TimeRange.ToTextDow(value.Value, false) + ", " + TimeRange.ToTextYD(value.Value) + Environment.NewLine + TimeRange.ToTextTS(value.Value);
        }
        /// <summary>
        /// Create and return text for tooltip for specified relative point
        /// </summary>
        /// <param name="relativePoint"></param>
        /// <returns></returns>
        private string _CreateToolTip(TimeRange timeRange)
        {
            if (timeRange == null) return null;
            TimeRange rounded = new TimeRange(this._CurrentSet.RoundDateToTick(timeRange.Begin.Value, AxisTickType.PixelTick), this._CurrentSet.RoundDateToTick(timeRange.End.Value, AxisTickType.PixelTick));
            return rounded.ToString();
        }
        private DateTime? _InteractiveShiftOrigin;
        private TimeRange _InteractiveZoomOrigin;
        private DateTime? _InteractiveZoomCenter;
        private Point? _MouseOverPoint;
        private Point? _MouseDownPoint;
        #endregion
        #region Draw to Graphic
        /// <summary>
        /// Draw current time axis to specified graphic.
        /// Coordinates are this.Area.
        /// </summary>
        /// <param name="e"></param>
        protected override void Draw(GInteractiveDrawArgs e)
        {
            if (this.Bounds.Width > 0 && this.Bounds.Height > 0)
            {
                using (GPainter.GraphicsUseText(e.Graphics, this.Bounds))
                {
                    this._DrawBackground(e.Graphics);
                    this._DrawMousePoint(e.Graphics);
                    this._DrawTicks(e.Graphics);
                    this._DrawInfo(e.Graphics);
                }
            }
        }
        private void _DrawBackground(Graphics graphics)
        {
            float morph = this.BackColor3DEffect;
            Color color = this.BackColor;
            Color color1 = color.Morph(Color.Black, morph);
            Color color2 = color.Morph(Color.LightGray, morph);
            using (System.Drawing.Drawing2D.LinearGradientBrush b = new System.Drawing.Drawing2D.LinearGradientBrush(this.Bounds, color1, color2, -90f))
            {
                graphics.FillRectangle(b, this.Bounds);
            }
        }
        private void _DrawMousePoint(Graphics graphics)
        {
            if (this._MouseOverPoint.HasValue)
            {
                Point mousePoint = this.Bounds.Location.Add(this._MouseOverPoint.Value);
                GPainter.DrawRadiance(graphics, mousePoint, this.Bounds, Color.FromArgb(128, Color.LightYellow));
            }
            else if (this._MouseDownPoint.HasValue)
            {
                Point mousePoint = this.Bounds.Location.Add(this._MouseDownPoint.Value);
                GPainter.DrawRadiance(graphics, mousePoint, this.Bounds, Color.FromArgb(64, Color.Violet));
            }
        }
        private void _DrawTicks(Graphics graphics)
        {
            if (this._TickList == null) return;
            int x = this.Bounds.X;
            int b = this.Bounds.Bottom - 1;
            decimal h = 14m;
            
            using (Pen pen = new Pen(Color.Black))
            using (SolidBrush brush = new SolidBrush(Color.Black))
            using (Font tickFontT = new Font(SystemFonts.DefaultFont.Name, 9f, FontStyle.Bold))
            using (Font tickFontS = new Font(SystemFonts.DefaultFont.Name, 8f, FontStyle.Regular))
            using (StringFormat sf = new StringFormat(StringFormatFlags.NoClip))
            {
                foreach (TimeAxisTick tick in this._TickList)
                {
                    Point point = new Point(tick.Pixel + x, b);
                    int height = (int)(h * tick.TickSize);
                    int width = ((tick.TickType == AxisTickType.Title || tick.TickType == AxisTickType.SubTitle) ? 2 : 1);
                    int top = b - height;
                    int bottom = b - 1 + width;
                    pen.Color = (tick.TickType == AxisTickType.RegularTick ? Color.Gray : Color.Black);
                    pen.Width = width;
                    graphics.DrawLine(pen, tick.Pixel, top, tick.Pixel, bottom);

                    if (tick.Text != null)
                    {
                        brush.Color = (tick.TickType == AxisTickType.Title ? Color.Black : Color.DimGray);
                        Font font = (tick.TickType == AxisTickType.Title ? tickFontT : tickFontS);
                        SizeF size = graphics.MeasureString(tick.Text, font);
                        PointF pt = new PointF(tick.Pixel, top - 6);
                        ContentAlignment alignment = (tick.Alignment == AxisTickAlignment.Begin ? ContentAlignment.BottomLeft : (tick.Alignment == AxisTickAlignment.End ? ContentAlignment.BottomRight : ContentAlignment.BottomCenter));
                        RectangleF area = size.AlignTo(pt, alignment, new Size(3, 3));
                        if (area.X < this.Bounds.X) area.X = this.Bounds.X;
                        if (area.Right > this.Bounds.Right) area.X = this.Bounds.Right - area.Width;
                        graphics.DrawString(tick.Text, font, brush, area, sf);
                    }
                }
            }
        }
        private void _DrawInfo(Graphics graphics)
        {
            if (this._CurrentSet == null) return;
            using (SolidBrush brush = new SolidBrush(Color.LightBlue))
            using (Font font = new Font(SystemFonts.DefaultFont.Name, 8f, FontStyle.Italic))
            using (StringFormat sf = new StringFormat(StringFormatFlags.NoClip))
            {
                string text = this._CurrentSet.SubTitleItem.Interval.ToString();
                SizeF size = graphics.MeasureString(text, font);
                PointF point = new PointF(this.Bounds.Right - 3, this.Bounds.Top + 1);
                RectangleF area = size.AlignTo(this.Bounds, ContentAlignment.TopCenter);
                area.X += 3;
                // RectangleF area = size.AlignTo(point, ContentAlignment.TopRight, new Size(3, 3));
                graphics.DrawString(text, font, brush, area, sf);
            }
        }
        #endregion
        #region Public services (conversion Time - Pixel)
        /// <summary>
        /// Returns DateTime for specified pixel. Pixel coordinates are on Control (i.e. time Begin is on pixel = this.Bounds.X).
        /// When TimeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="pixel"></param>
        /// <returns></returns>
        public DateTime? GetTimeOnPixelOnControl(int pixel)
        {
            return this.GetTimeOnPixelOnControl(pixel, AxisTickType.None);
        }
        /// <summary>
        /// Returns DateTime for specified pixel, rounded to specified tick. Pixel coordinates are on Control (i.e. time Begin is on pixel = this.Bounds.X).
        /// When TimeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="pixel"></param>
        /// <param name="roundToTick"></param>
        /// <returns></returns>
        public DateTime? GetTimeOnPixelOnControl(int pixel, AxisTickType roundToTick)
        {
            TimeSpan? time = this.GetTimeSpanForSize(pixel - this.Bounds.X);
            if (!time.HasValue) return null;
            DateTime date = this._TimeRange.Begin.Value.Add(time.Value);
            return this._CurrentSet.RoundDateToTick(date, roundToTick);
        }
        /// <summary>
        /// Returns DateTime for specified pixel. Pixel coordinates are on TimeAxis (i.e. time Begin is on pixel = 0).
        /// When TimeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="relativePixel"></param>
        /// <returns></returns>
        public DateTime? GetValueOnPixelOnAxis(int relativePixel)
        {
            return this.GetValueOnPixelOnAxis(relativePixel, AxisTickType.None);
        }
        /// <summary>
        /// Returns DateTime for specified pixel, rounded to specified tick. Pixel coordinates are on TimeAxis (i.e. time Begin is on pixel = 0).
        /// When TimeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="relativePixel"></param>
        /// <param name="roundToTick"></param>
        /// <returns></returns>
        public DateTime? GetValueOnPixelOnAxis(int relativePixel, AxisTickType roundToTick)
        {
            TimeSpan? time = this.GetTimeSpanForSize(relativePixel);
            if (!time.HasValue) return null;
            DateTime date = this._TimeRange.Begin.Value.Add(time.Value);
            return this._CurrentSet.RoundDateToTick(date, roundToTick);
        }
        /// <summary>
        /// Returns a TimeSpan for specified pixel size. For size == 0 return TimeSpan.Zero.
        /// When TimeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        public TimeSpan? GetTimeSpanForSize(int size)
        {
            if (!this._IsScalePrepared) return null;
            return TimeSpan.FromSeconds((double)((decimal)size * this._TimeScale));
        }
        /// <summary>
        /// Return a pixel coordinate for specified DateTime. Pixel coordinates are on Control (i.e. time Begin is on pixel = this.Bounds.X).
        /// When TimeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public Int32? GetPixelOnControlForTime(DateTime time)
        {
            if (!this._IsScalePrepared) return null;
            decimal seconds = (decimal)((TimeSpan)(time - this._TimeRange.Begin.Value)).TotalSeconds;
            decimal pixel = seconds / this._TimeScale;
            return this.Bounds.X + (int)Math.Round(pixel, 0);
        }
        /// <summary>
        /// Return a pixel coordinate for specified DateTime. Pixel coordinates are on TimeAxis (i.e. time Begin is on pixel = 0).
        /// When TimeAxis are not initialized, returns null.
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public Int32? GetPixelOnAxisForTime(DateTime time)
        {
            if (!this._IsScalePrepared) return null;
            decimal seconds = (decimal)((TimeSpan)(time - this._TimeRange.Begin.Value)).TotalSeconds;
            decimal pixel = seconds / this._TimeScale;
            return (int)Math.Round(pixel, 0);
        }
        /// <summary>
        /// Is scale prepared?
        /// TimeRange must be Filled, TimeScale and CurrentSet prepared (can call this._CalculateScaleOnly() when need).
        /// </summary>
        private bool _IsScalePrepared
        {
            get
            {
                if (this._TimeRange == null || !this._TimeRange.IsFilled) return false;
                if (this._TimeScale <= 0m || this._CurrentSet == null)
                    this._CalculateScaleOnly();
                if (this._CurrentSet == null) return false;
                return true;
            }
        }
        #endregion
        #region Private scale calculations, tick preparations, Arrangement and ArrangementSet selections
        /// <summary>
        /// Calculate Scale from TimeRange and Bounds
        /// </summary>
        private void _CalculateScale()
        {
            if (this.PixelSize <= 0m) return;
            this._CalculateScaleOnly();
            this._CalculateTicks();
        }
        /// <summary>
        /// Calculate TimeRange.End from TimeRange.Begin and Scale
        /// </summary>
        private void _CalculateEnd()
        {
            if (this.PixelSize <= 0m) return;
            if (this._TimeScale <= 0m || this._CurrentSet == null) return;
            if (this._TimeRange == null || !this._TimeRange.Begin.HasValue) return;

            DateTime begin = this._TimeRange.Begin.Value;
            DateTime end = begin.AddSeconds((double)(this._TimeScale * this.PixelSize));
            this._TimeRange = new TimeRange(begin, end);
            this._CalculateTicksOnly();
        }
        /// <summary>
        /// Calculate TimeScale and select appropriate ArrangementSet to _CurrentSet.
        /// Does not prepare Ticks.
        /// </summary>
        private void _CalculateScaleOnly()
        {
            if (this.PixelSize <= 0m) return;
            decimal? timeScale = this._GetTimeScale(this._TimeRange, true);
            if (timeScale.HasValue)
            {
                this.TimeScale = timeScale.Value;
                this._CurrentSet = this._Arrangement.SelectSetForScale(timeScale.Value);
            }
        }
        /// <summary>
        /// Returns time scale for specified TimeRange and current Bounds.
        /// Timescale can be aligned to MIN - MAX.
        /// When timeRange is empty or this.Bounds is empty, returns null.
        /// </summary>
        /// <param name="timeRange"></param>
        /// <param name="align"></param>
        /// <returns></returns>
        private decimal? _GetTimeScale(TimeRange timeRange, bool align)
        {
            if (timeRange == null || !timeRange.IsFilled) return null;
            decimal pixels = this.PixelSize;
            if (pixels <= 0m) return null;
            decimal timeScale = (decimal)timeRange.Time.Value.TotalSeconds / this.PixelSize;
            if (align)
                timeScale = _AlignTimeScale(timeScale);
            return timeScale;
        }
        /// <summary>
        /// Align specified timeScale to MIN-MAX range.
        /// </summary>
        /// <param name="timeScale"></param>
        /// <returns></returns>
        private static decimal _AlignTimeScale(decimal timeScale)
        {
            if (timeScale < TIME_SCALE_MIN) return TIME_SCALE_MIN;
            if (timeScale > TIME_SCALE_MAX) return TIME_SCALE_MAX;
            return timeScale;
        }
        /// <summary>
        /// Return true when timeScale is in range (MIN - MAX).
        /// </summary>
        /// <param name="timeScale"></param>
        /// <returns></returns>
        private static bool _IsTimeScaleInRange(decimal? timeScale)
        {
            if (!timeScale.HasValue) return false;
            return (timeScale >= TIME_SCALE_MIN && timeScale <= TIME_SCALE_MAX);
        }
        /// <summary>
        /// Calculate Ticks for Scale and TimeRange. Call _CalculateScaleOnly() if need.
        /// </summary>
        private void _CalculateTicks()
        {
            if (this._TimeScale <= 0m || this._CurrentSet == null)
                this._CalculateScaleOnly();
            if (this._CurrentSet == null) return;
            this._CalculateTicksOnly();
        }
        /// <summary>
        /// Only calculate TimeTicks for Scale and TimeRange, does not prepare Scale
        /// </summary>
        private void _CalculateTicksOnly()
        {
            if (this._CurrentSet == null) return;
            Dictionary<DateTime, TimeAxisTick> tickDict = new Dictionary<DateTime, TimeAxisTick>();

            this._CurrentSet.AddInitialTicks(tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.Title, tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.SubTitle, tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.SignificantTick, tickDict);
            this._CurrentSet.CalculateTicksLine(AxisTickType.RegularTick, tickDict);

            this._TickList = new List<TimeAxisTick>(tickDict.Values);
            this._TickList.Sort((a, b) => a.Value.CompareTo(b.Value));
        }
        /// <summary>
        /// Number of seconds to one pixel
        /// </summary>
        private decimal _TimeScale;
        private Arrangement _Arrangement;
        private ArrangementSet _CurrentSet;
        private List<TimeAxisTick> _TickList;
        #endregion
        #region class Arrangement, ArrangementSet, ArrangementItem
        /// <summary>
        /// Set of all disponible TimeAxis arrangements
        /// </summary>
        protected class Arrangement
        {
            public Arrangement(GTimeAxis axis)
            {
                this.Axis = axis;

                List<ArrangementSet> setList = new List<ArrangementSet>();
                string dmyyhm = "d.M.yyyy H:mm";
                string my = "M/yy";
                string y = "yy";
                string yy = "yyyy";
                string dmyy = "d.M.yyyy";
                string dm = "d.M.";
                string dmh = "d.M. H:mm";
                string hms = "H:mm:ss";
                string hm = "H:mm";
                string ms = "m:ss";
                string msf = "m:ss.f";
                string msff = "m:ss.ff";
                string msfff = "m:ss.fff";
                string sf = "s.f";
                string sff = "s.ff";
                string sfff = "s.fff";

                // TimeSpan specified in order (as unit on standard ruler): pixel, milimeters, 5milimeters, centimeter, 10centimeter:
                setList.Add(new ArrangementSet(TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(2), sfff, TimeSpan.FromMilliseconds(10), msfff, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(5), sfff, TimeSpan.FromMilliseconds(10), msfff, dmyyhm, AxisCycle.Day, this));

                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(5), TimeSpan.FromMilliseconds(10), sff, TimeSpan.FromMilliseconds(100), msf, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(2), TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(20), sff, TimeSpan.FromMilliseconds(100), msf, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(5), TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(50), sff, TimeSpan.FromMilliseconds(100), msf, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), sf, TimeSpan.FromSeconds(1), hms, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(20), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(200), sf, TimeSpan.FromSeconds(1), hms, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(500), sf, TimeSpan.FromSeconds(1), hms, dmyyhm, AxisCycle.Day, this));

                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(1), ms, TimeSpan.FromSeconds(10), hms, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(250), TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), ms, TimeSpan.FromSeconds(10), hms, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), ms, TimeSpan.FromSeconds(30), hms, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), ms, TimeSpan.FromMinutes(1), hm, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15), ms, TimeSpan.FromMinutes(1), hm, dmyyhm, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30), ms, TimeSpan.FromMinutes(2), hm, dmyyhm, AxisCycle.Day, this));

                setList.Add(new ArrangementSet(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15), TimeSpan.FromMinutes(1), hm, TimeSpan.FromMinutes(5), hm, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(2), hm, TimeSpan.FromMinutes(10), hm, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(30), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5), hm, TimeSpan.FromMinutes(10), hm, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(60), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), hm, TimeSpan.FromHours(1), hm, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), hm, TimeSpan.FromHours(1), hm, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(30), hm, TimeSpan.FromHours(1), hm, dmyy, AxisCycle.Day, this));

                setList.Add(new ArrangementSet(TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15), TimeSpan.FromHours(1), hm, TimeSpan.FromHours(6), dmh, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(30), TimeSpan.FromHours(2), hm, TimeSpan.FromHours(12), dmh, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(60), TimeSpan.FromHours(3), hm, TimeSpan.FromDays(1), dmyy, dmyy, AxisCycle.Day, this));

                setList.Add(new ArrangementSet(TimeSpan.FromMinutes(2), TimeSpan.FromHours(1), TimeSpan.FromHours(3), TimeSpan.FromHours(6), hm, TimeSpan.FromDays(1), dmyy, dmyy, AxisCycle.Day, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMinutes(5), TimeSpan.FromHours(1), TimeSpan.FromHours(6), TimeSpan.FromHours(12), dmh, TimeSpan.FromDays(2), dmyy, dmyy, AxisCycle.Week, this));

                setList.Add(new ArrangementSet(TimeSpan.FromMinutes(10), TimeSpan.FromHours(2), TimeSpan.FromHours(12), TimeSpan.FromDays(1), dm, TimeSpan.FromDays(7), dmyy, dmyy, AxisCycle.Week, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMinutes(15), TimeSpan.FromHours(3), TimeSpan.FromHours(24), TimeSpan.FromDays(2), dm, TimeSpan.FromDays(14), dmyy, dmyy, AxisCycle.Month, this));
                setList.Add(new ArrangementSet(TimeSpan.FromMinutes(30), TimeSpan.FromHours(12), TimeSpan.FromDays(1), TimeSpan.FromDays(7), dm, TimeSpan.FromDays(31), dmyy, dmyy, AxisCycle.Month, this));
                setList.Add(new ArrangementSet(TimeSpan.FromHours(1), TimeSpan.FromHours(12), TimeSpan.FromDays(2), TimeSpan.FromDays(14), dm, TimeSpan.FromDays(31), dmyy, dmyy, AxisCycle.Month, this));
                setList.Add(new ArrangementSet(TimeSpan.FromHours(3), TimeSpan.FromHours(84), TimeSpan.FromDays(7), TimeSpan.FromDays(31), dm, TimeSpan.FromDays(60), dmyy, dmyy, AxisCycle.Month, this));

                setList.Add(new ArrangementSet(TimeSpan.FromHours(12), TimeSpan.FromDays(7), TimeSpan.FromDays(31), TimeSpan.FromDays(92), dm, TimeSpan.FromDays(180), dmyy, dmyy, AxisCycle.Month, this));
                setList.Add(new ArrangementSet(TimeSpan.FromDays(2), TimeSpan.FromDays(31), TimeSpan.FromDays(91), TimeSpan.FromDays(183), dm, TimeSpan.FromDays(366), dmyy, dmyy, AxisCycle.Month, this));
                setList.Add(new ArrangementSet(TimeSpan.FromDays(7), TimeSpan.FromDays(91), TimeSpan.FromDays(183), TimeSpan.FromDays(366), yy, TimeSpan.FromDays(732), dmyy, dmyy, AxisCycle.Month, this));

                this.SetList = setList;
            }
            public GTimeAxis Axis { get; private set; }
            public List<ArrangementSet> SetList { get; private set; }
            /// <summary>
            /// Select best ArrangementSet for specified scale (scale = number of seconds per one pixel)
            /// </summary>
            /// <param name="scale"></param>
            /// <returns></returns>
            internal ArrangementSet SelectSetForScale(decimal scale)
            {
                ArrangementSet set = this.SetList.FirstOrDefault(s => s.GetDistanceSubTitleForScale(scale) >= 65m);
                if (set == null)
                    set = this.SetList[this.SetList.Count - 1];
                return set;
            }
        }
        /// <summary>
        /// One TimeAxis arrangement (set of four ticks = ArrangementItem)
        /// </summary>
        protected class ArrangementSet
        {
            public ArrangementSet(TimeSpan pixelTickTime, TimeSpan regularTickTime, TimeSpan significantTickTime, TimeSpan subTitleTime, string subTitleFormat, TimeSpan titleTime, string titleFormat, string initialFormat, AxisCycle cycle, Arrangement owner)
            {
                this.Owner = owner;
                this.Cycle = cycle;
                this.InitialItem = new ArrangementItem(AxisTickType.Initial, TimeSpan.Zero, initialFormat, this);
                this.TitleItem = new ArrangementItem(AxisTickType.Title, titleTime, titleFormat, this);
                this.SubTitleItem = new ArrangementItem(AxisTickType.SubTitle, subTitleTime, subTitleFormat, this);
                this.SignificantTickItem = new ArrangementItem(AxisTickType.SignificantTick, significantTickTime, null, this);
                this.RegularTickItem = new ArrangementItem(AxisTickType.RegularTick, regularTickTime, null, this);
                this.PixelTickItem = new ArrangementItem(AxisTickType.PixelTick, pixelTickTime, null, this);
            }
            public override string ToString()
            {
                return "SignificantTick: " + this.SignificantTickItem.ToString();
            }
            /// <summary>
            /// Time axis
            /// </summary>
            public GTimeAxis Axis { get { return this.Owner.Axis; } }
            /// <summary>
            /// Owner of this one set = Arrangement, set of all arrangements
            /// </summary>
            public Arrangement Owner { get; private set; }
            /// <summary>
            /// Initial title on begin (and end) of axis
            /// </summary>
            public ArrangementItem InitialItem { get; private set; }
            /// <summary>
            /// Cycle of axis, select day for Title item
            /// </summary>
            public AxisCycle Cycle { get; private set; }
            /// <summary>
            /// Big tick, as 10 cm on ruler
            /// </summary>
            public ArrangementItem TitleItem { get; private set; }
            /// <summary>
            /// Standard numbered tick, as 1 cm on ruler
            /// </summary>
            public ArrangementItem SubTitleItem { get; private set; }
            /// <summary>
            /// Significant non-numbered tick, as 5 mm on ruler
            /// </summary>
            public ArrangementItem SignificantTickItem { get; private set; }
            /// <summary>
            /// Standard non-numbered tick, as 1 mm on ruler
            /// </summary>
            public ArrangementItem RegularTickItem { get; private set; }
            /// <summary>
            /// Nonvisible ticks, exists only for round time to "pixel" coordinates.
            /// </summary>
            public ArrangementItem PixelTickItem { get; private set; }
            /// <summary>
            /// Return distance between two tick of SubTitle type for specified scale (scale = number of seconds per one pixel).
            /// </summary>
            /// <param name="scale"></param>
            /// <returns></returns>
            internal decimal GetDistanceSubTitleForScale(decimal scale)
            {
                return this.SubTitleItem.GetDistanceForScale(scale);
            }
            /// <summary>
            /// Returns specified datetime, rounded to specified Tick
            /// </summary>
            /// <param name="date"></param>
            /// <param name="roundToTick"></param>
            /// <returns></returns>
            internal DateTime? RoundDateToTick(DateTime date, AxisTickType roundToTick)
            {
                ArrangementItem item = GetArrangementItemForTick(roundToTick);
                return (item != null ? item.RoundDateToTick(date) : date);
            }
            /// <summary>
            /// Add tick for begin and end of timerange
            /// </summary>
            /// <param name="tickDict"></param>
            internal void AddInitialTicks(Dictionary<DateTime, TimeAxisTick> tickDict)
            {
                this.InitialItem.AddInitialTicks(tickDict);
            }
            /// <summary>
            /// Prepare Ticks for specified TickType to tick Dictionary
            /// </summary>
            /// <param name="tickType"></param>
            /// <param name="tickDict"></param>
            internal void CalculateTicksLine(AxisTickType tickType, Dictionary<DateTime, TimeAxisTick> tickDict)
            {
                ArrangementItem item = GetArrangementItemForTick(tickType);
                if (item != null)
                    item.CalculateTicksLine(tickDict);
            }
            /// <summary>
            /// Returns instance of ArrangementItem for specified Tick type
            /// </summary>
            /// <param name="tickType"></param>
            /// <returns></returns>
            internal ArrangementItem GetArrangementItemForTick(AxisTickType tickType)
            {
                switch (tickType)
                {
                    case AxisTickType.PixelTick: return this.PixelTickItem;
                    case AxisTickType.RegularTick: return this.RegularTickItem;
                    case AxisTickType.SignificantTick: return this.SignificantTickItem;
                    case AxisTickType.SubTitle: return this.SubTitleItem;
                    case AxisTickType.Title: return this.TitleItem;
                }
                return null;
            }
        }
        /// <summary>
        /// Specification of one tick on one TimeAxis arrangement: interval, time format string, support for calculations
        /// </summary>
        protected class ArrangementItem
        {
            public ArrangementItem(AxisTickType tickType, TimeSpan interval, string timeFormat, ArrangementSet owner)
            {
                this.Owner = owner;
                this.TickType = tickType;
                this.Interval = interval;
                this._Seconds = (decimal)interval.TotalSeconds;
                this.TimeFormat = timeFormat;
            }
            public override string ToString()
            {
                return this.TickType.ToString() + "; " + this.Interval.ToString();
            }
            /// <summary>
            /// Time axis
            /// </summary>
            public GTimeAxis Axis { get { return this.Owner.Axis; } }
            /// <summary>
            /// Owner of this one set = ArrangementSet, set of arrangements of four TickType
            /// </summary>
            public ArrangementSet Owner { get; private set; }
            /// <summary>
            /// Type of this ticks
            /// </summary>
            public AxisTickType TickType { get; private set; }
            /// <summary>
            /// Interval between ticks
            /// </summary>
            public TimeSpan Interval { get; private set; }
            /// <summary>
            /// User string format for display time on this ticks
            /// </summary>
            public string TimeFormat { get; private set; }
            /// <summary>
            /// Return distance between two tick of this type for specified scale (scale = number of seconds per one pixel).
            /// </summary>
            /// <param name="scale"></param>
            /// <returns></returns>
            internal decimal GetDistanceForScale(decimal scale)
            {
                return this._Seconds / scale;
            }
            private decimal _Seconds;
            /// <summary>
            /// Standard length of tick between 0 and 1. 
            /// Title has length = 1.00, SubTitle = 0.90, SignificantTick = 0.75, RegularTick = 0.60.
            /// </summary>
            public decimal TickSize
            {
                get
                {
                    if (!this._TickLength.HasValue)
                    {
                        switch (this.TickType)
                        {
                            case AxisTickType.Initial:
                                this._TickLength = 2.00m;
                                break;
                            case AxisTickType.Title:
                                this._TickLength = 1.00m;
                                break;
                            case AxisTickType.SubTitle:
                                this._TickLength = 0.90m;
                                break;
                            case AxisTickType.SignificantTick:
                                this._TickLength = 0.75m;
                                break;
                            case AxisTickType.RegularTick:
                                this._TickLength = 0.60m;
                                break;
                            default:
                                this._TickLength = 0.50m;
                                break;
                        }
                    }
                    return this._TickLength.Value;
                }
            }
            private decimal? _TickLength;
            /// <summary>
            /// Returns specified datetime to this ticks.
            /// </summary>
            /// <param name="date"></param>
            /// <returns></returns>
            internal DateTime? RoundDateToTick(DateTime date)
            {
                return TimeRange.RoundDateTime(date, this.Interval, RoundMode.Math);
            }
            /// <summary>
            /// Add tick for begin and end of timerange
            /// </summary>
            /// <param name="tickDict"></param>
            internal void AddInitialTicks(Dictionary<DateTime, TimeAxisTick> tickDict)
            {
                TimeRange timeRange = this.Axis.TimeRange;

                string textBegin = this.GetTimeText(timeRange.Begin.Value);
                this.AddOneTick(tickDict, timeRange.Begin.Value, textBegin, AxisTickAlignment.Begin);

                string textEnd = this.GetTimeText(timeRange.End.Value);
                if (textEnd != textBegin)
                    this.AddOneTick(tickDict, timeRange.End.Value, textEnd, AxisTickAlignment.End);
            }
            /// <summary>
            /// Store to Dictionary (tickDict) all ticks for current item and current state of Axis (TimeRange, Scale).
            /// When Dictionary contains a key for any time on line of this item, does not add new tick.
            /// Dictionary must be filled from top to bottom (from Title to Regular items).
            /// </summary>
            /// <param name="tickDict"></param>
            internal void CalculateTicksLine(Dictionary<DateTime, TimeAxisTick> tickDict)
            {
                if (this.Interval.Ticks == 0L) return;

                TimeRange timeRange = this.Axis.TimeRange;
                DateTime time = TimeRange.RoundDateTime(timeRange.Begin.Value, this.Interval, RoundMode.Ceiling);
                while (time <= timeRange.End.Value)
                {
                    string text = this.GetTimeText(time);
                    this.AddOneTick(tickDict, time, text, AxisTickAlignment.Center);
                    time = TimeRange.RoundAddTime(time, this.Interval);
                }
            }
            private string GetTimeText(DateTime time)
            {
                return (this.TimeFormat != null ? time.ToString(this.TimeFormat) : null);
            }
            private void AddOneTick(Dictionary<DateTime, TimeAxisTick> tickDict, DateTime time, string text, AxisTickAlignment alignment)
            {
                if (!tickDict.ContainsKey(time))
                {
                    TimeAxisTick tick = new TimeAxisTick(this.TickType, time, this.Axis.GetPixelOnControlForTime(time).Value, this.TickSize, text, alignment);
                    tickDict.Add(time, tick);
                }
            }
        }
        protected enum AxisCycle
        {
            None,
            Day,
            Week,
            Month,
            Year
        }
        #endregion
    }
    #region class TimeAxisTick, AxisTick<T>, enum AxisTickType, AxisOrientation
    /// <summary>
    /// One real tick on TimeAxis
    /// </summary>
    public class TimeAxisTick : AxisTick<DateTime>
    {
        public TimeAxisTick(AxisTickType tickType, DateTime value, int x, decimal tickSize, string text, AxisTickAlignment alignment)
            : base(tickType, value, x, tickSize, text, alignment)
        { }
    }

    #endregion
}
